# -*- coding: utf-8 -*-
# @Time    : 2024/6/12 13:46
# @Author  : yujiahao
# @File    : 16_iteration.py
# @description: Python中的迭代器


"""
Java中的迭代器:
1. 定义与实现:
   - Java中的迭代器是通过实现java.util.Iterator接口来实现的。
   - Iterator接口包含三个主要方法：hasNext()、next()和remove()。
   - hasNext()：返回布尔值，指示是否有更多元素。
   - next()：返回下一个元素。
   - remove()：删除迭代器返回的最后一个元素（可选操作）。

2. 使用示例:
   import java.util.ArrayList;
   import java.util.Iterator;

   public class IteratorExample {
       public static void main(String[] args) {
           ArrayList<String> list = new ArrayList<>();
           list.add("A");
           list.add("B");
           list.add("C");

           Iterator<String> iterator = list.iterator();
           while (iterator.hasNext()) {
               String element = iterator.next();
               System.out.println(element);
           }
       }
   }

3. 应用场景:
   - 遍历集合（如ArrayList、HashSet等）中的元素。
   - 在遍历过程中删除元素。
   - 处理需要顺序访问的自定义数据结构。

4. 注意事项:
   - 调用next()之前必须先调用hasNext()，否则可能抛出NoSuchElementException。
   - remove()方法是可选操作，不是所有的迭代器都支持。

Scala中的迭代器:
1. 定义与实现:
   - Scala中的迭代器是通过实现scala.collection.Iterator特性（trait）来实现的。
   - Iterator特性包含两个主要方法：hasNext和next。
   - hasNext：返回布尔值，指示是否有更多元素。
   - next：返回下一个元素。

2. 使用示例:
   object IteratorExample extends App {
       val list = List("A", "B", "C")
       val iterator = list.iterator

       while (iterator.hasNext) {
           val element = iterator.next()
           println(element)
       }
   }

3. 应用场景:
   - 遍历集合（如List、Set等）中的元素。
   - 处理流式数据（如文件读取、网络流等）。
   - 使用迭代器进行懒加载操作，避免一次性加载大量数据。

4. 注意事项:
   - 迭代器是一次性的，遍历完后需要重新获取新的迭代器。
   - 迭代过程中不能修改集合，否则会抛出ConcurrentModificationException。

Python中的迭代器:
1. 定义与实现:
   - Python中的迭代器是通过实现__iter__()和__next__()方法的对象来实现的。
   - __iter__()：返回迭代器对象本身。
   - __next__()：返回下一个元素，如果没有元素则抛出StopIteration异常。

2. 使用示例:
   class MyIterator:
       def __init__(self, data):
           self.data = data
           self.index = 0

       def __iter__(self):
           return self

       def __next__(self):
           if self.index < len(self.data):
               result = self.data[self.index]
               self.index += 1
               return result
           else:
               raise StopIteration

   my_iter = MyIterator([1, 2, 3])
   for item in my_iter:
       print(item)

3. 内置迭代器:
   - Python提供了许多内置的迭代器，例如list、tuple、dict等。
   - 使用内置函数iter()可以获取对象的迭代器，使用next()可以获取下一个元素。
   - 示例:
     my_list = [1, 2, 3]
     my_iter = iter(my_list)
     print(next(my_iter))  # 输出: 1
     print(next(my_iter))  # 输出: 2
     print(next(my_iter))  # 输出: 3

4. 应用场景:
   - 遍历集合（如列表、元组、字典等）中的元素。
   - 处理大文件或数据流，避免一次性加载所有数据。
   - 实现生成器函数，使用yield关键字生成迭代器。

5. 注意事项:
   - 迭代器是一次性的，遍历完后需要重新获取新的迭代器。
   - 调用next()时，如果没有更多元素，会抛出StopIteration异常。
   - 使用生成器函数可以简化迭代器的实现，yield语句可以生成值并暂停函数的执行。
"""

# todo 1、可迭代对象

'''
可迭代对象（Iterable）是指那些实现了特殊方法 __iter__() 的对象，这些对象可以返回一个迭代器（Iterator）。
在 Python 中，所有集合类型（如列表、元组、字典、集合等）都是可迭代对象。


【定义】
        一个对象如果实现了 __iter__() 方法，并且该方法返回一个迭代器对象，那么这个对象就是可迭代的。
        迭代器对象是指实现了 __iter__() 和 __next__() 方法的对象。

【实现】
        可迭代对象可以通过实现 __iter__() 方法来创建。
        迭代器对象则需要实现 __iter__() 和 __next__() 方法。

【小结】

        - 从字面意思来说：可迭代对象就是一个可以重复取值的实实在在的东西。

        - 从专业角度来说：但凡内部含有__iter__方法的对象，都是可迭代对象。

        - 可迭代对象可以通过判断该对象是否有__iter__方法来判断。

        - 可迭代对象的优点：
            
            - 1.可以直观的查看里面的数据。
            
        - 可迭代对象的缺点：

            - 1.占用内存。
    
            - 2.可迭代对象不能迭代取值（除去索引，key以外）。

        - 那么这个缺点有人就提出质疑了，即使抛去索引,key以外，这些我可以通过for循环进行取值呀！对，他们都可以通过for循环进行取值，其实for循环在底层做了一个小小的转化，就是先将可迭代对象转化成迭代器，然后在进行取值的。那么接下来，我们就看看迭代器是个什么鬼。

'''


# 查看对象内部方法

'''可以通过dir() 去判断一个对象具有什么方法,dir()会返回一个列表，这个列表中含有该对象的以字符串的形式所有方法名。这样我们就可以判断python中的一个对象是不是可迭代对象了'''

s1 = 'alex'
i = 100
# 打印对象方法
print(dir(s1))
# 查看是否是可迭代对象，就是看是否实现了__inter__方法
print('__iter__' in dir(i))  # False
print('__iter__' in dir(s1))  # True


# todo 2、迭代器

'''
迭代器（Iterator）是一个实现了 __iter__() 和 __next__() 方法的对象。迭代器用于逐个访问集合中的元素，而无需了解集合的底层实现。

【定义】
        从字面意思来说：迭代器，是一个可以迭代取值的工具，器：在这里当做工具比较合适。

        从专业角度来说：迭代器是这样的对象：实现了无参数的__next__方法，返回序列中的下一个元素，如果没有元素了，那么抛出StopIteration异常.python中的迭代器还实现了__iter__方法，因此迭代器也可以迭代。 出自《流畅的python》
       
        简单来说迭代器必须实现两个方法：__iter__() 和 __next__()。
        __iter__() 方法返回迭代器对象本身。
        __next__() 方法返回下一个元素，如果没有更多元素，则抛出 StopIteration 异常。
【实现】
        Python 中的许多内置对象（如列表、元组、字典等）都可以返回一个迭代器。
        可以通过自定义类来实现自己的迭代器。

说一个事情：
【文件句柄（File Handle）】是一个对象，通过它可以对文件进行读写操作。就是之前读写文件的那个
    文件句柄是由 open() 函数返回的，代表了一个打开的文件，并提供了多种方法来处理文件内容。
'''
# 如何判断该对象是否是迭代器

 # 请判断这些对象：str list tuple dict set range 文件句柄 哪个是迭代器，哪个是可迭代对象：

o1 = 'alex'
o2 = [1, 2, 3]
o3 = (1, 2, 3)
o4 = {'name': '好人','age': 2000}
o5 = {1, 2, 3}
f = open('../../../output/file',encoding='utf-8', mode='w')
print('__iter__' in dir(o1))  # True
print('__iter__' in dir(o2))  # True
print('__iter__' in dir(o3))  # True
print('__iter__' in dir(o4))  # True
print('__iter__' in dir(o5))  # True
print('__iter__' in dir(f))  # True

print('__next__' in dir(o1))  # False
print('__next__' in dir(o2))  # False
print('__next__' in dir(o3))  # False
print('__next__' in dir(o4))  # False
print('__next__' in dir(o5))  # False
print('__next__' in dir(f))  # True
f.close()
# 通过以上代码可以验证，之前我们学过的这些对象，只有文件句柄是迭代器，剩下的那些数据类型都是可迭代对象。

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 3、可迭代对象如何转化成迭代器
'''
【区别与推荐使用】
    封装与可读性：
        - iter() 函数是对 __iter__() 方法的封装，提供了一个更简洁和直观的方式来获取迭代器。
        - iter(l1) 比 l1.__iter__() 更符合 Python 的编码习惯，更容易理解。
    一致性与通用性：
        - iter() 函数不仅可以用于可迭代对象，还可以用于实现了 __iter__() 或 __getitem__() 方法的自定义对象。
        - iter() 函数在处理不同类型的可迭代对象时提供了一致的接口。
    推荐使用：
        - 通常情况下，推荐使用 iter() 函数来获取迭代器，因为它是更通用和直观的方式
【小结】
 - 从字面意思来说：迭代器就是可以迭代取值的工具。

 - 从专业角度来说：在python中，内部含有__Iter__方法并且含有__next__方法的对象就是迭代器。

 - 迭代器的优点：

     - 节省内存。=> 迭代器在内存中相当于只占一个数据的空间：因为每次取值都上一条数据会在内存释放，加载当前的此条数据。
    
     - 惰性机制。=> next一次，取一个值，绝不过多取值。

     - 有一个迭代器模式可以很好的解释上面这两条：迭代是数据处理的基石。扫描内存中放不下的数据集时，我们要找到一种惰性获取数据项的方式，即按需一次获取一个数据项。这就是迭代器模式。

 - 迭代器的缺点：

     - 不能直观的查看里面的数据。
    
     - 取值时不走回头路，只能一直向下取值。
     
             l1 = [1, 2, 3, 4, 5, 6]
            obj = iter(l1)
            
            for i in range(2):
                print(next(obj))
            
            for i in range(2):
                print(next(obj))
'''
# 使用__iter__()
l1 = [1, 2, 3, 4, 5, 6]
obj = l1.__iter__()

#使用iter() 推荐
obj = iter(l1)
print(obj)

# 迭代器取值

'''可迭代对象是不可以一直迭代取值的（除去用索引，切片以及Key），但是转化成迭代器就可以了，迭代器是利用__next__()进行取值'''

l1 = [1, 2, 3,]
obj = l1.__iter__()  # 或者 iter(l1)
# print(obj)  # <list_iterator object at 0x000002057FE1A3C8>
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
# ret = obj.__next__()  # StopIteration
# print(ret)
# 迭代器利用next取值：一个next取对应的一个值，如果迭代器里面的值取完了，还要next，
# 那么就报StopIteration的错误。

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# while模拟for的内部循环机制

'''刚才我们提到了，for循环的循环对象一定要是可迭代对象，但是这不意味着可迭代对象就可以取值，因为for循环的内部机制是：将可迭代对象转换成迭代器，然后利用next进行取值，最后利用异常处理处理StopIteration抛出的异常。'''

l1 = [1, 2, 3, 4, 5, 6]
# 1 将可迭代对象转化成迭代器
obj = iter(l1)
# 2,利用while循环，next进行取值
while 1:
    # 3,利用异常处理终止循环
    try:
        print(next(obj))
    except StopIteration:
        break

# todo 5、可迭代对象与迭代器对比


'''
 我们今天比较深入的了解了可迭代对象与迭代器，接下来我们说一下这两者之间比较与应用：

     可迭代对象：
    
        是一个私有的方法比较多，操作灵活（比如列表，字典的增删改查，字符串的常用操作方法等）,比较直观，但是占用内存，而且不能直接通过循环迭代取值的这么一个数据集。
    
     应用：当你侧重于对于数据可以灵活处理，并且内存空间足够，将数据集设置为可迭代对象是明确的选择。
    
     迭代器：
    
        是一个非常节省内存，可以记录取值位置，可以直接通过循环+next方法取值，但是不直观，操作方法比较单一的数据集。
    
     应用：当你的数据量过大，大到足以撑爆你的内存或者你以节省内存为首选因素时，将数据集设置为迭代器是一个不错的选择。（可参考为什么python把文件句柄设置成迭代器）。
 
 '''